﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VerySmartSoftware.Emulator
{
    public class CPU6502
    {
        public bool isExecuting;
        public UInt16 PC;           // Program Counter
        private byte reg_A;          // reg_A Register
        private byte reg_X;              // reg_X Register
        private byte reg_Y;              // reg_Y Register
        private byte SR;             // Status Register
        private byte SP;             // Stack Pointer

        byte[] ram;         // RAM
        byte[] rom_kernel;  // Kernel ROM
        byte[] rom_basic;   // Basic ROM
        UInt16 stack_ptr;   // Stack Pointer

        public Dictionary<string, byte> OpCodes = new Dictionary<string, byte>();

        public CPU6502()
        {
            Initialise();
        }

        public void Run()
        {
            // Start now
            isExecuting = true;

            while (isExecuting)
            {
                // GetNextMem the next byte
                // And then move the program counter forward
                byte opCode = ram[PC++];

                // Execute the opcode
                Execute(opCode);

            }
        }

        private byte GetNextMem()
        {
            return (byte)ram[PC++];
        }

        private byte MemoryRead(UInt16 address)
        {
            return (byte)ram[address];
        }

        private void MemoryWrite(UInt16 address, byte value)
        {
            ram[address] = value;
        }

        private void push_stack(byte value)
        {
            ram[stack_ptr--] = value;   // Stack beings from $01FF and works down
            if (stack_ptr < 0x0100)     // Push beyond 0x0100 (256 bytes)?
                stack_ptr = 0x01FF;     // Yes, then the SP will wrap to the start 
        }

        private byte pop_stack()
        {
            byte value = MemoryRead(stack_ptr++);
            if (stack_ptr > 0x01FF)
                stack_ptr = 0x01FF;     // 
            return value;
        }


        private void Initialise()
        {
            ram = new byte[0xFFFF];     // (64K)
            rom_kernel = new byte[8192];
            rom_basic = new byte[8192];
            stack_ptr = 0x01FF;         // The 256 byte stack is stored at $0100-$01FF (starts at $01FF)
            PC = 0x00;                  // Program counter starts at $0
        }

        // Zero Flag (bit 1)
        public bool Z
        {
            get { return ((SR & 2) == 2); }
        }

        // Carry Flag (bit 0)
        public bool C
        {
            get { return ((SR & 1) == 1); }
        }

        // Negative Flag (bit 7)
        public bool N
        {
            get { return ((SR & 128) == 128); }
        }

        // Overflow Flag (bit 6)
        public bool V
        {
            get { return ((SR & 8) == 8); }
        }

        // Break Flag (bit 4)
        public bool B
        {
            get { return ((SR & 16) == 16); }
        }

        // Decimal Flag (bit 3)
        public bool D
        {
            get { return ((SR & 8) == 8); }
        }

        // Interupt Flag (bit 2)
        public bool I
        {
            get { return ((SR & 4) == 4); }
        }

        public byte Reg_A
        {
            get { return reg_A; }
        }

        public byte Reg_X
        {
            get { return reg_X; }
        }

        public byte Reg_Y
        {
            get { return reg_Y; }
        }

        public byte[] Ram
        {
            get { return ram; }
        }

        private void Execute(byte opCode)
        {
            byte low;
            byte high;

            switch (opCode)
            {
                // LDA immediate addressing
                case 0xA9:
                    // Read the next byte only
                    reg_A = GetNextMem();
                    break;

                // STA absolute addressing
                case 0x8D:
                    // Get the address location to store
                    low = GetNextMem();
                    high = GetNextMem();

                    // And store it
                    // (We left-shift the high byte by 8 bits and add the low byte to give our 16-bit address)
                    MemoryWrite((UInt16)(high << 8 | low), reg_A);
                    break;

                // STX absolute addressing
                case 0x8E:
                    // Get the address location to store
                    low = GetNextMem();
                    high = GetNextMem();

                    // And store it
                    // (We left-shift the high byte by 8 bits and add the low byte to give our 16-bit address)
                    MemoryWrite((UInt16)(high << 8 | low), reg_X);
                    break;

                // STY absolute addressing
                case 0x8F:
                    // Get the address location to store
                    low = GetNextMem();
                    high = GetNextMem();

                    // And store it
                    // (We left-shift the high byte by 8 bits and add the low byte to give our 16-bit address)
                    MemoryWrite((UInt16)(high << 8 | low), reg_Y);
                    break;

                // PHA - Push Accumlator
                case 0x48:
                    push_stack(reg_A);  // Push the contents of the accumulator onto the stack
                    break;

                // PHP - Push Status Register (status flags)
                case 0x08:
                    push_stack(SR);     // Push the contents of the SR onto the stack
                    break;

                case 0x00:
                // drop through

                default:
                    isExecuting = false;
                    break;
            }
        }
    }
}
